﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Xml.Linq;
using XmlDatabase.Core.Properties;

namespace XmlDatabase.Core
{
    public sealed class XDatabase : IDisposable
    {
        public void Dispose() { }
        private XDatabase() { }

        internal XIdsManager ids;
        private XTaskManager taskManager;
        private static String DATABASECONFIGFILE = "CORE.XML";
        public String Version { get => XUtility.EngineVersion; }

        private String _fullName = "";
        public String FullName { get => _fullName; }

        private TextWriter _log;
        public TextWriter Log { get => _log; set => _log = value; }

        private String _userName;
        public String UserName { get => _userName; set => _userName = value; }

        private Boolean _autoSubmit = true;
        public Boolean AutoSubmit { get => _autoSubmit; set => _autoSubmit = value; }

        private String _applicationName;
        public String ApplicationName { get => _applicationName; set => _applicationName = value; }

        private XDatabase(String fullName) : this()
        {
            this._fullName = fullName;
            this.ApplicationName = AppDomain.CurrentDomain.FriendlyName;
            this.UserName = Thread.CurrentPrincipal.Identity.Name;
            this.Log = new XLogWriter(this);
            this.ids = XIdsManager.GetInstance(this.FullName);
            this.taskManager = new XTaskManager(this);
            this.Log.WriteEx(Resources.DatabaseOpened, true);
        }

        public static XDatabase Open(String datebase)
        {
            XDatabase.DATABASECONFIGFILE = Path.Combine(datebase, "core.xml");
            try
            {
                if (Directory.Exists(datebase) && File.Exists(XDatabase.DATABASECONFIGFILE))
                {
                    return new XDatabase(datebase);
                    throw new FileNotFoundException();
                }
                else
                {
                    Directory.CreateDirectory(datebase);
                    new XDocument(new Object[1]
                    {
                     new XElement("Database", new Object[3]
                    {
                     new XAttribute("CreateTime", DateTime.Now),
                     new XAttribute("Version", XUtility.EngineVersion),
                     new XElement("Types")
                     })}).Save(XDatabase.DATABASECONFIGFILE);
                    return new XDatabase(datebase);
                }
            }
            catch (Exception ex)
            {
                throw new XDatabaseException("GeneralException", new String[1] { ex.Message });
            }
        }

        public void Close()
        {
            this.Log.WriteLine(Properties.Resources.DatabaseClosed);
            this.Log.Close();
        }

        public void Configure(XTypeRegistration[] items)
        {
            XDocument xdocument = XDocument.Load(XDatabase.DATABASECONFIGFILE);
            XElement xelement = xdocument.Root.Element("Types");
            items.ToList<XTypeRegistration>().ForEach((item) =>
           {
               String path = Path.Combine(this.FullName, "Entities\\" + item.FullName);
               Directory.CreateDirectory(path);
               Directory.CreateDirectory(path + "\\Data");
               Directory.CreateDirectory(path + "\\Index");
               if (xelement.Elements("Typse").FirstOrDefault<XElement>((f) => f.Attribute("FullName").Value == item.FullName) != null)
               {
                   item.ConvertToXml("Type", Guid.Empty);
               }
               else
               {
                   xelement.Add(item.ConvertToXml("Type", Guid.Empty));
               }
               this.Log.WriteEx(String.Format(Resources.TypeConfiguration, item.FullName));
           }
            );
            this.Log.Flush();
            xdocument.Save(XDatabase.DATABASECONFIGFILE);
        }

        public void Store(Object instance)
        {
            XElement xelement = XDocument.Load(XDatabase.DATABASECONFIGFILE).Root.Element("Types").Elements("Type").Where<XElement>((e) => e.Attribute("FullName").Value == instance.GetType().FullName).FirstOrDefault<XElement>();
            if (xelement == null)
            {
                this.Configure(new XTypeRegistration[1] { new XTypeRegistration() { FullName = instance.GetType().FullName } });
                this.Store(instance, false);
            }
            else
            {
                this.Store(instance, Boolean.Parse(xelement.Attribute("SingleRowPerFile").Value));
            }
        }
        public void Store(Object instance, Boolean singleFile)
        {
            String typeFullName = instance.GetType().FullName;
            String name = instance.GetType().Name;
            if (XDocument.Load(XDatabase.DATABASECONFIGFILE).Root.Element("Types").Elements("Type").Where<XElement>((e) => e.Attribute("FullName").Value == typeFullName).FirstOrDefault<XElement>() == null)
            {
                this.Configure(new XTypeRegistration[1]
                 {
                      new XTypeRegistration() { FullName = typeFullName }
                 });
            }
            String str = String.Format("{4}\\{0}\\{1}\\{2}\\{3}.xml", "Entities", typeFullName, "Data", typeFullName, this._fullName);
            if (this._autoSubmit)
            {
                XDocument xdocument1;
                if (!File.Exists(str))
                {
                    xdocument1 = new XDocument(new Object[1]
                    {
                     new XElement( "XMLDatabase-Entities")
                    });
                }
                else
                {
                    xdocument1 = XDocument.Load(str);
                }
                XDocument xdocument2 = xdocument1;
                Guid id = this.ids.GetObjectId(instance);
                if (id != Guid.Empty)
                {
                    xdocument2.Root.Elements(name).Where<XElement>(((e) => e.Attribute("_uuid").Value == id.ToString())).FirstOrDefault<XElement>().ReplaceWith(instance.ConvertToXml(id));
                    this.Log.WriteEx(String.Format(Resources.UpdateObject, id, instance.ToString()), true);
                }
                else
                {
                    id = Guid.NewGuid();
                    XElement xml = instance.ConvertToXml(id);
                    xdocument2.Root.Add(xml);
                    this.Log.WriteEx(String.Format(Resources.InsertObject, id, instance.ToString()), true);
                }
                xdocument2.Save(str);
            }
            else
            {
                this.taskManager.AddTask(new XChangeItem()
                {
                    Action = XChangeAction.AddOrUpdate,
                    UserData = instance
                });
            }
        }
        public IEnumerable<T> Query<T>()
        {
            String fullName = typeof(T).FullName;
            String str = String.Format("{4}\\{0}\\{1}\\{2}\\{3}.xml", "Entities", fullName, "Data", fullName, this._fullName);
            if (!File.Exists(str))
            {
                return Enumerable.Empty<T>();
            }
            IEnumerable<XElementExtensions.Entity> entities = XDocument.Load(str).Root.Elements().Select<XElement, XElementExtensions.Entity>((e) => e.ConvertToObject<T>());
            List<T> source = new List<T>();
            foreach (XElementExtensions.Entity entity in entities)
            {
                this.ids.Set(entity.Instance, entity.Id);
                source.Add((T)entity.Instance);
            }
            this.Log.WriteEx(String.Format(Resources.QueryObject, source.Count<T>()), true);
            return source;
        }
        public IEnumerable<T> Get<T>()
        {
            return this.Query<T>();
        }
        public void Delete(Object instance)
        {
            if (this._autoSubmit)
            {
                Guid id = this.ids.GetObjectId(instance);
                if (id == Guid.Empty)
                {
                    return;
                }
                this.ids.Remove(instance);
                String fullName = instance.GetType().FullName;
                String str = String.Format("{4}\\{0}\\{1}\\{2}\\{3}.xml", "Entities", fullName, "Data", fullName, this._fullName);
                if (!File.Exists(str))
                {
                    return;
                }
                XDocument xdocument = XDocument.Load(str);
                xdocument.Root.Elements().FirstOrDefault<XElement>((e => e.Attribute("_uuid").Value == id.ToString())).Remove();
                xdocument.Save(str);
                this.Log.WriteEx(String.Format(Resources.DeleteObject, id, instance), true);
            }
            else
            {
                this.taskManager.AddTask(new XChangeItem()
                {
                    Action = XChangeAction.Delete,
                    UserData = instance
                });
            }
        }
        public void Delete(Object[] objects)
        {
            foreach (Object instance in objects)
            {
                this.Delete(instance);
            }
        }
        public XSubmitStatus SubmitChanges(Boolean continueOnError)
        {
            return this.taskManager.Execute(continueOnError, false);
        }

        public XSubmitStatus SubmitChanges()
        {
            return this.SubmitChanges(true);
        }

        public XSubmitStatus SubmitChangesWithTransaction()
        {
            return this.taskManager.Execute(false, true);
        }

        public XTransaction BeginTransaction()
        {
            return new XTransaction(this);
        }
    }
}

